import {
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  EventEmitter,
  Input,
  OnChanges,
  OnDestroy,
  OnInit,
  Output,
  SimpleChange,
  SimpleChanges, TemplateRef,
  ViewChild,
  ViewEncapsulation,
} from '@angular/core';
import { of, Observable, Subscription } from 'rxjs';
import { filter } from 'rxjs/operators';

import {
  ApesSafeAny,
  ShowUploadListInterface,
  UploadChangeParam,
  UploadFile,
  UploadFilter,
  UploadListType, UploadTransformFileType,
  UploadType,
  UploadXHRArgs,
  ZipButtonOptions,
} from './interface';
import { ApesUploadBtnComponent } from './apes-upload-btn.component';
import { ApesUploadListComponent } from './apes-upload-list.component';
import { InputBoolean, InputNumber, toBoolean, toNumber } from '@shared/apes/core/util/convert';
import { ApesI18nService } from '@shared/apes/i18n';

@Component({
  selector: 'apes-upload',
  exportAs: 'apesUpload',
  templateUrl: './apes-upload.component.html',
  preserveWhitespaces: false,
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  host: {
    '[class.ant-upload-picture-card-wrapper]': 'apesListType === "picture-card"',
  },
})
export class ApesUploadComponent implements OnInit, OnChanges, OnDestroy {
  private i18n$: Subscription;
  @ViewChild('uploadComp', { static: false }) uploadComp: ApesUploadBtnComponent;
  @ViewChild('listComp', { static: false }) listComp: ApesUploadListComponent;
  // tslint:disable-next-line:no-any
  locale: ApesSafeAny = {};

  // #region fields

  @Input() apesType: UploadType = 'select';
  @Input() @InputNumber() apesLimit = 0;
  @Input() @InputNumber() apesSize = 0;


  @Input() apesFileType: string;
  @Input() apesAccept: string | string[];
  @Input() apesAction: string | ((file: UploadFile) => string | Observable<string>);
  @Input() @InputBoolean() apesDirectory = false;
  @Input() @InputBoolean() apesOpenFileDialogOnClick = true;
  @Input() apesBeforeUpload: (file: UploadFile, fileList: UploadFile[]) => boolean | Observable<boolean>;
  @Input() apesCustomRequest: (item: UploadXHRArgs) => Subscription;
  @Input() apesData: {} | ((file: UploadFile) => {} | Observable<{}>);
  @Input() apesFilter: UploadFilter[] = [];
  @Input() apesFileList: UploadFile[] = [];
  @Input() @InputBoolean() apesDisabled = false;
  @Input() apesHeaders: {} | ((file: UploadFile) => {} | Observable<{}>);
  @Input() apesListType: UploadListType = 'text';
  @Input() @InputBoolean() apesMultiple = false;
  @Input() apesName = 'file';

  private _showUploadList: boolean | ShowUploadListInterface = true;

  @Input()
  set apesShowUploadList(value: boolean | ShowUploadListInterface) {
    this._showUploadList = typeof value === 'boolean' ? toBoolean(value) : value;
  }

  get apesShowUploadList(): boolean | ShowUploadListInterface {
    return this._showUploadList;
  }

  @Input() @InputBoolean() apesShowButton = true;
  @Input() @InputBoolean() apesWithCredentials = false;

  @Input() apesRemove: (file: UploadFile) => boolean | Observable<boolean>;
  @Input() apesPreview: (file: UploadFile) => void;
  @Input() apesPreviewFile: (file: UploadFile) => Observable<string>;
  @Input() apesTransformFile: (file: UploadFile) => UploadTransformFileType;
  @Input() apesDownload: (file: UploadFile) => void;
  @Input() apesIconRender: TemplateRef<void>;

  @Output() readonly apesChange: EventEmitter<UploadChangeParam> = new EventEmitter<UploadChangeParam>();
  @Output() readonly apesFileListChange: EventEmitter<UploadFile[]> = new EventEmitter<UploadFile[]>();

  _btnOptions: ZipButtonOptions;

  private zipOptions(): this {
    if (typeof this.apesShowUploadList === 'boolean' && this.apesShowUploadList) {
      this.apesShowUploadList = {
        showPreviewIcon: true,
        showRemoveIcon: true,
        showDownloadIcon: true,
      };
    }
    // filters
    const filters: UploadFilter[] = this.apesFilter.slice();
    if (this.apesMultiple && this.apesLimit > 0 && filters.findIndex(w => w.name === 'limit') === -1) {
      filters.push({
        name: 'limit',
        fn: (fileList: UploadFile[]) => fileList.slice(-this.apesLimit),
      });
    }
    if (this.apesSize > 0 && filters.findIndex(w => w.name === 'size') === -1) {
      filters.push({
        name: 'size',
        fn: (fileList: UploadFile[]) => fileList.filter(w => (w.size / 1024) <= this.apesSize),
      });
    }
    if (this.apesFileType && this.apesFileType.length > 0 && filters.findIndex(w => w.name === 'type') === -1) {
      const types = this.apesFileType.split(',');
      filters.push({
        name: 'type',
        fn: (fileList: UploadFile[]) => fileList.filter(w => ~types.indexOf(w.type!)),
      });
    }
    this._btnOptions = {
      disabled: this.apesDisabled,
      accept: this.apesAccept,
      action: this.apesAction,
      directory: this.apesDirectory,
      openFileDialogOnClick: this.apesOpenFileDialogOnClick,
      beforeUpload: this.apesBeforeUpload,
      customRequest: this.apesCustomRequest,
      data: this.apesData,
      headers: this.apesHeaders,
      name: this.apesName,
      multiple: this.apesMultiple,
      withCredentials: this.apesWithCredentials,
      filters,
      transformFile: this.apesTransformFile,
      onStart: this.onStart,
      onProgress: this.onProgress,
      onSuccess: this.onSuccess,
      onError: this.onError,
    };
    return this;
  }

  // #endregion

  constructor(private cdr: ChangeDetectorRef, private i18n: ApesI18nService) {
  }

  // #region upload

  private fileToObject(file: UploadFile): UploadFile {
    return {
      lastModified: file.lastModified,
      lastModifiedDate: file.lastModifiedDate,
      name: file.filename || file.name,
      size: file.size,
      type: file.type,
      uid: file.uid,
      response: file.response,
      error: file.error,
      percent: 0,
      // tslint:disable-next-line:no-any
      originFileObj: file as ApesSafeAny,
    };
  }

  private getFileItem(file: UploadFile, fileList: UploadFile[]): UploadFile {
    return fileList.filter(item => item.uid === file.uid)[0];
  }

  private removeFileItem(file: UploadFile, fileList: UploadFile[]): UploadFile[] {
    return fileList.filter(item => item.uid !== file.uid);
  }

  // private genErr(file: UploadFile): string {
  //   return file.response && typeof file.response === 'string' ?
  //     file.response :
  //     (file.error && file.error.statusText) || this.locale.uploadError;
  // }

  private onStart = (file: UploadFile): void => {
    if (!this.apesFileList) {
      this.apesFileList = [];
    }
    const targetItem = this.fileToObject(file);
    targetItem.status = 'uploading';
    this.apesFileList = this.apesFileList.concat(targetItem);
    this.apesFileListChange.emit(this.apesFileList);
    this.apesChange.emit({ file: targetItem, fileList: this.apesFileList, type: 'start' });
    this.detectChangesList();
  };

  private onProgress = (e: { percent: number }, file: UploadFile): void => {
    const fileList = this.apesFileList;
    const targetItem = this.getFileItem(file, fileList);
    targetItem.percent = e.percent;
    this.apesChange.emit({
      event: e,
      file: { ...targetItem },
      fileList: this.apesFileList,
      type: 'progress',
    });
    this.detectChangesList();
  };

  private onSuccess = (res: {}, file: UploadFile): void => {
    const fileList = this.apesFileList;
    const targetItem = this.getFileItem(file, fileList);
    targetItem.status = 'done';
    targetItem.response = res;
    this.apesChange.emit({
      file: { ...targetItem },
      fileList,
      type: 'success',
    });
    this.detectChangesList();
  };

  private onError = (err: {}, file: UploadFile): void => {
    const fileList = this.apesFileList;
    const targetItem = this.getFileItem(file, fileList);
    targetItem.error = err;
    targetItem.status = 'error';
    // targetItem.message = this.genErr(targetItem);
    this.apesChange.emit({
      file: { ...targetItem },
      fileList,
      type: 'error',
    });
    this.detectChangesList();
  };

  // #endregion

  // #region drag

  private dragState: string;

  // skip safari bug
  fileDrop(e: DragEvent): void {
    if (e.type === this.dragState) {
      return;
    }
    this.dragState = e.type;
    this.setClassMap();
  }

  // #endregion

  // #region list

  private detectChangesList(): void {
    this.cdr.detectChanges();
    this.listComp.detectChanges();
  }

  onRemove = (file: UploadFile): void => {
    this.uploadComp.abort(file);
    file.status = 'removed';
    const fnRes = typeof this.apesRemove === 'function' ? this.apesRemove(file) : this.apesRemove == null ? true : this.apesRemove;
    (fnRes instanceof Observable ? fnRes : of(fnRes))
      .pipe(filter((res: boolean) => res))
      .subscribe(() => {
        this.apesFileList = this.removeFileItem(file, this.apesFileList);
        this.apesChange.emit({
          file,
          fileList: this.apesFileList,
          type: 'removed',
        });
        this.apesFileListChange.emit(this.apesFileList);
        this.cdr.detectChanges();
      });
  };

  // #endregion

  // #region styles

  private prefixCls = 'apes-upload';
  classList: string[] = [];

  private setClassMap(): void {
    let subCls: string[] = [];
    if (this.apesType === 'drag') {
      if (this.apesFileList.some(file => file.status === 'uploading')) {
        subCls.push(`${this.prefixCls}-drag-uploading`);
      }
      if (this.dragState === 'dragover') {
        subCls.push(`${this.prefixCls}-drag-hover`);
      }
    } else {
      subCls = [
        `${this.prefixCls}-select-${this.apesListType}`,
      ];
    }

    this.classList = [
      this.prefixCls,
      `${this.prefixCls}-${this.apesType}`,
      ...subCls,
      (this.apesDisabled && `${this.prefixCls}-disabled`) || ''
    ].filter(item => !!item);

    this.cdr.detectChanges();
  }

  // #endregion

  ngOnInit(): void {
    this.i18n$ = this.i18n.localeChange.subscribe(() => {
      this.locale = this.i18n.getLocaleData('Upload');
      this.detectChangesList();
    });
  }

  ngOnChanges(): void {
    this.zipOptions().setClassMap();
  }

  ngOnDestroy(): void {
    this.i18n$.unsubscribe();
  }
}
